/* bspr.vala
 *
 * Copyright (C) 2010, 2011  Richard Talbot-Watkins
 * Copyright (C) 2010, 2011  Dave Jeffery
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Authors:
 *  Richard Talbot-Watkins <richtw1@gmail.com>
 * 	Dave Jeffery <david.richard.jeffery@gmail.com>
 */

using Gee;
using Xml;

/**
 * Error domain for .bspr import and export operations.
 */
public errordomain BeebSpriter.BsprError {
	READ_ERROR,
	WRITE_ERROR
}

/**
 * This class allows BeebSpriter to read and write .bspr format xml files.
 *
 * @author Richard Talbot-Watkins <richtw1@gmail.com>
 * @author Dave Jeffery <david.richard.jeffery@gmail.com>
 * @version 2011.0106
 * @since 0.1
 */
public class BeebSpriter.Bspr : GLib.Object {
	/**
	 * Deserializes a .bpsr XML file back into a SpriteSheet instance.
	 *
	 * @param file A sprite sheet instance serialized into a .bspr xml file.
	 * @return A sprite sheet object created by deserializing the .bspr.
	 * @throws BsprError If file empty, not found or has bad permissions.
	 */
	public static SpriteSheet read (File file) throws BsprError {
		/* Get filename as string. */
		var path = file.get_path ();

		/* Read .bspr file.*/
		Xml.Doc* doc = Parser.read_file (path, null, ParserOption.NOBLANKS);

        if (doc == null) {
            throw new BsprError.READ_ERROR 
				("Check it is a .bspr file and its permissions are correct.");
        }

		/* Read the root node of the document. */
		Xml.Node* sprite_sheet_root = doc->get_root_element ();

        if (sprite_sheet_root == null) {
            delete doc;
            throw new BsprError.READ_ERROR ("The file is empty or corrupt.");
        }

		/* Check the root element is called SpriteSheet. */
		if (sprite_sheet_root->name != "SpriteSheet") {
            delete doc;
            throw new BsprError.READ_ERROR 
									("The file is not a valid .bspr file.");
		}

		/* Get Mode attribute */
		var mode = sprite_sheet_root->get_prop ("Mode").to_int ();

		/* Create SpriteSheet instance. */
		var sprite_sheet = new SpriteSheet.with_mode (mode);

		/* Background colour. */
		var back_colours = new HashMap<string, SpriteSheet.BackColour> ();
		back_colours["Transparent"] = SpriteSheet.BackColour.THEME_DEFAULT;
		back_colours["Black"] = SpriteSheet.BackColour.BLACK;
		back_colours["Red"] = SpriteSheet.BackColour.RED;
		back_colours["Green"] = SpriteSheet.BackColour.GREEN;
		back_colours["Yellow"] = SpriteSheet.BackColour.YELLOW;
		back_colours["Blue"] = SpriteSheet.BackColour.BLUE;
		back_colours["Magenta"] = SpriteSheet.BackColour.MAGENTA;
		back_colours["Cyan"] = SpriteSheet.BackColour.CYAN;
		back_colours["White"] = SpriteSheet.BackColour.WHITE;

		Xml.Node* sprite_sheet_node = 
								sprite_sheet_root->first_element_child ();
		sprite_sheet.background_colour = 
							back_colours[sprite_sheet_node->get_content ()];

		/* Default palette. */
		var colours = new HashMap<string, BeebPalette.Colour> ();
		colours["Black"] = BeebPalette.Colour.BLACK;
		colours["Red"] = BeebPalette.Colour.RED;
		colours["Green"] = BeebPalette.Colour.GREEN;
		colours["Yellow"] = BeebPalette.Colour.YELLOW;
		colours["Blue"] = BeebPalette.Colour.BLUE;
		colours["Magenta"] = BeebPalette.Colour.MAGENTA;
		colours["Cyan"] = BeebPalette.Colour.CYAN;
		colours["White"] = BeebPalette.Colour.WHITE;
		
		BeebPalette.Colour[] palette = {};
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		for (Xml.Node* iter = sprite_sheet_node->children; 
								iter != null; 
								iter = iter->next) {
		    string node_content = iter->get_content ();
			palette += colours[node_content];
		}
		sprite_sheet.default_palette = palette;

		/* Default show grid lines. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.default_show_grid_lines = 
							sprite_sheet_node->get_content ().to_bool ();

		/* Horizontal block dividers. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.horizontal_block_dividers = 
							sprite_sheet_node->get_content ().to_int ();

		/* Vertical block dividers. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.vertical_block_dividers = 
							sprite_sheet_node->get_content ().to_int ();

		/* Default zoom. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.default_zoom = 
							sprite_sheet_node->get_content ().to_int ();

		/* Has set export settings. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.has_export_settings = 
							sprite_sheet_node->get_content ().to_bool ();

		/* Sprite layout. */
		var layout = new HashMap<string, SpriteSheet.SpriteDataLayout> ();
		layout["RowMajor"] = SpriteSheet.SpriteDataLayout.ROW_MAJOR;
		layout["ColumnMajor"] = SpriteSheet.SpriteDataLayout.COLUMN_MAJOR;

		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.sprite_layout = 
								layout[sprite_sheet_node->get_content ()];

		/* Should break sprites. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.should_break_sprites = 
							sprite_sheet_node->get_content ().to_bool ();

		/* Sub sprite width. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.sub_sprite_width = 
							sprite_sheet_node->get_content ().to_int ();

		/* Sub sprite height. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.sub_sprite_height = 
							sprite_sheet_node->get_content ().to_int ();

		/* Should export separate mask. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.should_export_separate_mask = 
							sprite_sheet_node->get_content ().to_bool ();

		/* Should generate header. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.should_generate_header = 
							sprite_sheet_node->get_content ().to_bool ();

		/* Assembler syntax. */
		sprite_sheet_node = sprite_sheet_node->next_element_sibling ();
		sprite_sheet.assembler_syntax = sprite_sheet_node->get_content ();

		/* SpriteList element. */
		Xml.Node* sprite_list = sprite_sheet_node->next_element_sibling ();

		for (Xml.Node* sprite = sprite_list->children; 
								sprite != null; 
								sprite = sprite->next) {

			/* Sprite name. */
			var name = sprite->get_prop ("Name");

			/* Sprite width. */
			var width = sprite->get_prop ("Width").to_int ();

			/* Sprite height. */
			var height = sprite->get_prop ("Height").to_int ();

			/* Sprite Bitmap. */
			Xml.Node* bitmap = sprite->first_element_child ();
			var bitmap_base64 = bitmap->get_content ();
			var bitmap_uchar = Base64.decode (bitmap_base64);
			uint8[] bitmap_uint8 = {}; 

			foreach (uchar c in bitmap_uchar) {
				bitmap_uint8 += (uint8) c;
			}

			/* Sprite palette. */
			BeebPalette.Colour[] sprite_palette = {};

			Xml.Node* palette_element = bitmap->next_element_sibling ();
			for (Xml.Node* colour = palette_element->children; 
									colour != null; 
									colour = colour->next) {
				var colour_name = colour->get_content ();
				sprite_palette += colours[colour_name];
			}

			/* Create a sprite instance.*/
			var sprite_instance = new Sprite (name, width, height, 
														sprite_palette);
			sprite_instance.bitmap = bitmap_uint8;

			/* Add to sprite sheet sprite list. */
			sprite_sheet.add_sprite (sprite_instance);
		}

		delete doc;

		return sprite_sheet;
	}

	/**
	 * Serializes SpriteSheet instance into a .bspr XML file.
	 *
	 * @param file The filename of the destination .bspr XML file.
	 */
	public static void write (SpriteSheet sprite_sheet, File file) 
														throws BsprError {
		/* Get filename as string. */
		var path = file.get_path ();

		var writer = new Xml.TextWriter.filename (path);

        if (writer == null) {
            throw new BsprError.WRITE_ERROR ("Unable to create the file.");
        }

		writer.set_indent (true);
		writer.set_indent_string ("  ");

		writer.start_document ("1.0", "utf-8");
		writer.start_element ("SpriteSheet");
		writer.write_attribute ("xmlns:xsi", 
								"http://www.w3.org/2001/XMLSchema-instance");
		writer.write_attribute ("xmlns:xsd", 
								"http://www.w3.org/2001/XMLSchema");
		writer.write_attribute ("Mode", sprite_sheet.mode.to_string ());

		/* Background colour. */
		writer.write_element ("BackgroundColour",
								sprite_sheet.background_colour.to_string ());

		/* Default palette. */
		writer.start_element ("DefaultPalette");
		foreach (var c in sprite_sheet.default_palette) {
			writer.write_element ("Colour", c.to_string ());
		}
		writer.end_element();

		/* Default show grid lines. */
		writer.write_element ("DefaultShowGridLines",
						sprite_sheet.default_show_grid_lines.to_string ());

		/* Horizontal block dividers. */
		writer.write_element ("HorizontalBlockDividers",
						sprite_sheet.horizontal_block_dividers.to_string ());

		/* Vertical block dividers. */
		writer.write_element ("VerticalBlockDividers",
						sprite_sheet.vertical_block_dividers.to_string ());

		/* Default zoom. */
		writer.write_element ("DefaultZoom",
						sprite_sheet.default_zoom.to_string ());

		/* Has set export settings. */
		writer.write_element ("HasSetExportSettings",
						sprite_sheet.has_export_settings.to_string ());

		/* Sprite layout. */
		writer.write_element ("SpriteLayout",
						sprite_sheet.sprite_layout.to_string ());

		/* Should break sprites. */
		writer.write_element ("ShouldBreakSprites",
						sprite_sheet.should_break_sprites.to_string ());

		/* Sub sprite width. */
		writer.write_element ("SubSpriteWidth",
						sprite_sheet.sub_sprite_width.to_string ());

		/* Sub sprite height. */
		writer.write_element ("SubSpriteHeight",
						sprite_sheet.sub_sprite_height.to_string ());

		/* Should export separate mask. */
		writer.write_element ("ShouldExportSeparateMask",
						sprite_sheet.should_export_separate_mask.to_string ());

		/* Should generate header. */
		writer.write_element ("ShouldGenerateHeader",
						sprite_sheet.should_generate_header.to_string ());

		/* Assembler syntax. */
		writer.write_element ("AssemblerSyntax",
						sprite_sheet.assembler_syntax);

		/* SpriteList element. */
		writer.start_element ("SpriteList");
		var sprite_list = sprite_sheet.get_sprite_list ();
		foreach (var sprite in sprite_list) {
			writer.start_element ("Sprite");

			/* Sprite Name. */
			writer.write_attribute ("Name", sprite.name.to_string ());

			/* Sprite Width. */
			writer.write_attribute ("Width", sprite.width.to_string ());

			/* Sprite Height. */
			writer.write_attribute ("Height", sprite.height.to_string ());

			/* Sprite Bitmap. */
			uchar[] bitmap_uchar = {};
			foreach (var byte in sprite.bitmap) {
				bitmap_uchar += (uchar) byte;
			}
			writer.write_element ("Bitmap", Base64.encode (bitmap_uchar));
			
			/* Sprite Palette. */
			writer.start_element ("Palette");
			foreach (var colour in sprite.palette) {
				writer.write_element ("Colour", colour.to_string ());
			}
			writer.end_element();

			writer.end_element();
		}
		writer.end_element();

		writer.end_element();
		writer.end_document();

		writer.flush();
	}
}
